I. Introduction & Motivation

Transit-Oriented Development, or TOD for short, is at the heart of the City of Dallas’ long-range land use plan, forwardDallas!. Dallas is home to 74 passenger rail stations in the system of Dallas Area Rapid Transit (DART), and each presenting a unique opportunity to develop a new mixed-use neighborhood. DART is a transit agency serving the Dallas–Fort Worth metroplex of Texas. It operates buses, light rail, commuter rail, and high-occupancy vehicle lanes in Dallas and twelve of its suburbs. DART reported the 92,000 average weekday riders in 4th quarter of 2019. In terms of the commute situation of residents in Dallas, 3.8% of Dallas residents commute via public transit while 50,000 Dallas residents commute over an hour each way to/from work.

When DART originally proposed the system back in the 1980s, the hope was that new rail stations would spur on new developments of dense housing and commercial real estate. And so far, a new study has just shown how productive and important DART is to the overall North Texas economy, generating billions of dollars in revenue, taxes and jobs.

Accordingly, this project is aimed at provide an overview on whether residents would like to pay more for the sake of the potential benefits from TOD in Dallas.

II. Setup

In this section, we loaded necessary libraries, created plot theme options and map theme options, and identified functions of quintile breaks for further analysis.

# 1. load Libraries
knitr::opts_chunk$set(echo = TRUE)
options(scipen=999)


library(tidyverse)
library(tidycensus)
library(sf)
library(gridExtra)
library(grid)
library(knitr)
library(kableExtra)
library(rmarkdown)

mapTheme <- function(base_size = 12) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(size = 16,colour = "black"),
    plot.subtitle=element_text(face="italic"),
    plot.caption=element_text(hjust=0),
    axis.ticks = element_blank(),
    panel.background = element_blank(),axis.title = element_blank(),
    axis.text = element_blank(),
    axis.title.x = element_blank(),
    axis.title.y = element_blank(),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=2),
    strip.text.x = element_text(size = 14))
}

plotTheme <- function(base_size = 12) {
  theme(
    text = element_text( color = "black"),
    plot.title = element_text(size = 16,colour = "black"),
    plot.subtitle = element_text(face="italic"),
    plot.caption = element_text(hjust=0),
    axis.ticks = element_blank(),
    panel.background = element_blank(),
    panel.grid.major = element_line("grey80", size = 0.1),
    panel.grid.minor = element_blank(),
    panel.border = element_rect(colour = "black", fill=NA, size=2),
    strip.background = element_rect(fill = "grey80", color = "white"),
    strip.text = element_text(size=12),
    axis.title = element_text(size=12),
    axis.text = element_text(size=10),
    plot.background = element_blank(),
    legend.background = element_blank(),
    legend.title = element_text(colour = "black", face = "italic"),
    legend.text = element_text(colour = "black", face = "italic"),
    strip.text.x = element_text(size = 14)
  )
}

# 3. Identify functions
qBr <- function(df, variable, rnd) {
  if (missing(rnd)) {
    as.character(quantile(round(df[[variable]],0),
                          c(.01,.2,.4,.6,.8), na.rm=T))
  } else if (rnd == FALSE | rnd == F) {
    as.character(formatC(quantile(df[[variable]]), digits = 3),
                 c(.01,.2,.4,.6,.8), na.rm=T)
  }
}

q5 <- function(variable) {as.factor(ntile(variable, 5))}


multipleRingBuffer <- function(inputPolygon, maxDistance, interval) 
{
  #create a list of distances that we'll iterate through to create each ring
  distances <- seq(0, maxDistance, interval)
  #we'll start with the second value in that list - the first is '0'
  distancesCounter <- 2
  #total number of rings we're going to create
  numberOfRings <- floor(maxDistance / interval)
  #a counter of number of rings
  numberOfRingsCounter <- 1
  #initialize an otuput data frame (that is not an sf)
  allRings <- data.frame()
  
  #while number of rings  counteris less than the specified nubmer of rings
  while (numberOfRingsCounter <= numberOfRings) 
  {
    #if we're interested in a negative buffer and this is the first buffer
    #(ie. not distance = '0' in the distances list)
    if(distances[distancesCounter] < 0 & distancesCounter == 2)
    {
      #buffer the input by the first distance
      buffer1 <- st_buffer(inputPolygon, distances[distancesCounter])
      #different that buffer from the input polygon to get the first ring
      buffer1_ <- st_difference(inputPolygon, buffer1)
      #cast this sf as a polygon geometry type
      thisRing <- st_cast(buffer1_, "POLYGON")
      #take the last column which is 'geometry'
      thisRing <- as.data.frame(thisRing[,ncol(thisRing)])
      #add a new field, 'distance' so we know how far the distance is for a give ring
      thisRing$distance <- distances[distancesCounter]
    }
    
    
    #otherwise, if this is the second or more ring (and a negative buffer)
    else if(distances[distancesCounter] < 0 & distancesCounter > 2) 
    {
      #buffer by a specific distance
      buffer1 <- st_buffer(inputPolygon, distances[distancesCounter])
      #create the next smallest buffer
      buffer2 <- st_buffer(inputPolygon, distances[distancesCounter-1])
      #This can then be used to difference out a buffer running from 660 to 1320
      #This works because differencing 1320ft by 660ft = a buffer between 660 & 1320.
      #bc the area after 660ft in buffer2 = NA.
      thisRing <- st_difference(buffer2,buffer1)
      #cast as apolygon
      thisRing <- st_cast(thisRing, "POLYGON")
      #get the last field
      thisRing <- as.data.frame(thisRing$geometry)
      #create the distance field
      thisRing$distance <- distances[distancesCounter]
    }
    
    #Otherwise, if its a positive buffer
    else 
    {
      #Create a positive buffer
      buffer1 <- st_buffer(inputPolygon, distances[distancesCounter])
      #create a positive buffer that is one distance smaller. So if its the first buffer
      #distance, buffer1_ will = 0. 
      buffer1_ <- st_buffer(inputPolygon, distances[distancesCounter-1])
      #difference the two buffers
      thisRing <- st_difference(buffer1,buffer1_)
      #cast as a polygon
      thisRing <- st_cast(thisRing, "POLYGON")
      #geometry column as a data frame
      thisRing <- as.data.frame(thisRing[,ncol(thisRing)])
      #add teh distance
      thisRing$distance <- distances[distancesCounter]
    }  
    
    #rbind this ring to the rest of the rings
    allRings <- rbind(allRings, thisRing)
    #iterate the distance counter
    distancesCounter <- distancesCounter + 1
    #iterate the number of rings counter
    numberOfRingsCounter <- numberOfRingsCounter + 1
  }
  
  #convert the allRings data frame to an sf data frame
  allRings <- st_as_sf(allRings)
}

palette5 <- c("#CCCCFF","#CC99FF","#9966CC","#663399","#330066")

# Load API
census_api_key("a7edab3d7c3df571998caab5a3cc12a4ec8d8b61" , overwrite  = TRUE)

III. Data Manipulation and Visualization

1. Data Wrangling

Three datasets were used in this project:

  • Census Data: demographic variables from the ACS 2009 and 2017 for census tracts in Dallas County.

  • DART Railway Station Data: Dallas transit data in geojson format from the DART transit system

  • Crime Data: Case-level burglary crime data between Jan 2017 to Dec 2017

1.1 Fetch Census Data

Variables we were interested in this projects are:

  • Total population

  • White population percentage

  • Percentage of adults who are Bachelor Degree holders

  • Median household income (adjusted for inflation)

  • Median rent (adjusted for inflation)

# ---- 2009 Census Data -----
tracts09 <-  
  get_acs(geography = "tract", variables = c("B25026_001E","B02001_002E","B15001_050E",
                                             "B15001_009E","B19013_001E","B25058_001E",
                                             "B06012_002E"), 
          year=2009, state= 48, county= 113, geometry=T) %>% 
  st_transform('EPSG:32138')


tracts09 <- 
  tracts09 %>%
  dplyr::select( -NAME, -moe) %>%
  spread(variable, estimate) %>%
  dplyr::select(-geometry) %>%
  rename(TotalPop = B25026_001, 
         Whites = B02001_002,
         FemaleBachelors = B15001_050, 
         MaleBachelors = B15001_009,
         MedHHInc = B19013_001, 
         MedRent = B25058_001,
         TotalPoverty = B06012_002)


tracts09 <- 
  tracts09 %>%
  mutate(pctWhite = ifelse(TotalPop > 0, Whites / TotalPop, 0),
         pctBachelors = ifelse(TotalPop > 0, ((FemaleBachelors + MaleBachelors) / TotalPop * 100), 0),
         pctPoverty = ifelse(TotalPop > 0, TotalPoverty / TotalPop, 0),
         year = "2009") %>%
  dplyr::select(-Whites,-FemaleBachelors,-MaleBachelors,-TotalPoverty)

# ---- 2017 Census Data -----

tracts17 <- 
  get_acs(geography = "tract", variables = c("B25026_001E","B02001_002E","B15001_050E",
                                             "B15001_009E","B19013_001E","B25058_001E",
                                             "B06012_002E"), 
          year=2017, state= 48, county= 113, geometry=T, output="wide") %>%
  st_transform('EPSG:32138') %>%
  rename(TotalPop = B25026_001E, 
         Whites = B02001_002E,
         FemaleBachelors = B15001_050E, 
         MaleBachelors = B15001_009E,
         MedHHInc = B19013_001E, 
         MedRent = B25058_001E,
         TotalPoverty = B06012_002E) %>%
  dplyr::select(-NAME, -starts_with("B")) %>% #-starts_with("B") awesome!
  mutate(pctWhite = ifelse(TotalPop > 0, Whites / TotalPop,0),
         pctBachelors = ifelse(TotalPop > 0, ((FemaleBachelors + MaleBachelors) / TotalPop * 100),0),
         pctPoverty = ifelse(TotalPop > 0, TotalPoverty / TotalPop, 0),
         year = "2017") %>%
  dplyr::select(-Whites, -FemaleBachelors, -MaleBachelors, -TotalPoverty) 

# --- Combining 09 and 17 data ----

allTracts <- rbind(tracts09,tracts17)

1.2 Load Dallas Transit Data

Showed in Figure 1.1, DART has 74 stations, spreading from Downtown to multiple directions updated to 2017.

ggplot() + 
  geom_sf(data= Dallas) +
  geom_sf(data=D_Stops, 
          aes(color = 'orange'), 
          show.legend = "point", size= 2) +
  scale_colour_manual(values = c("orange"),
                      labels = 'Stations',
                      name = ' ') +
  labs(title="Dallas Transit Stops", 
       subtitle=" ", 
       caption="Figure 1.1") + 
  plotTheme() +
  theme(plot.title = element_text(color = "darkred", size=15, face="bold"))

1.3 Identifying TOD Tracts

With census data and transit data ready, let’s jump into TOD analysis! Similarly to the textbook, we created a 0.5 mile buffer around the transit stops and selected centroidized tracts if the center of a census tract was located within this buffer zone. In this way, we selected TOD tracts.

Indicated in Figure 1.2, despite slight decrease in the northeast Dallas, TOD tracts did not change much in the past 10 years.

D_Buffers <- 
  rbind(
    st_buffer(D_Stops, 2640) %>%
      mutate(Legend = "Buffer") %>%
      dplyr::select(Legend),
    st_union(st_buffer(D_Stops, 2640)) %>%
      st_sf() %>%
      mutate(Legend = "Unioned Buffer"))
buffer <- filter(D_Buffers, Legend=="Unioned Buffer")

selectCentroids <-
  st_centroid(tracts09)[buffer,] %>%
  st_drop_geometry() %>%
  left_join(dplyr::select(tracts09, GEOID)) %>%
  st_sf() %>%
  dplyr::select(TotalPop) %>%
  mutate(Selection_Type = "Select by Centroids")

head(selectCentroids,5)

# Census Data in Group by TODs
allTracts.group <- 
  rbind(
    st_centroid(allTracts)[buffer,] %>%
      st_drop_geometry() %>%
      left_join(allTracts) %>%
      st_sf() %>%
      mutate(TOD = "TOD"),
    st_centroid(allTracts)[buffer, op = st_disjoint] %>%
      st_drop_geometry() %>%
      left_join(allTracts) %>%
      st_sf() %>%
      mutate(TOD = "Non-TOD")) %>%
  mutate(MedRent.inf = ifelse(year == "2009", MedRent * 1.14, MedRent))  # inflation

# TOD and Non-TOD between 2009 and 2017
ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = TOD)) +
  labs(title = "Time/Space Groups",
       subtitle = '',
       caption = 'Figure 1.2') +
  facet_wrap(~year)+
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

2. Changes in Census Variables Over Time and Space

In Section 2, we examined the effects of time (years) and space (TOD vs non-TOD) on our four key Census variables:

  • Median rents obviously increased from 2009 to 2017 but the increasing rate of TOD tracts was higher compared to non-TOD tracts. Similar trend could also be found in percentage of Bachelor degree holders and median household income.

  • However, there was no obvious change on percentage of white group between two time across TOD or non-TOD tracts.

2.1 Median Rent Comparison between 2009 and 2017

ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = q5(MedRent.inf))) +
  scale_fill_manual(values = palette5,
                    labels = qBr(allTracts.group, "MedRent.inf"),
                    name = "Rent\n(Quintile Breaks)") +
  labs(title = "Median Rent (w/inflation) 2009-2017", 
       subtitle = "Real dollars \n",
       caption = 'Figure 2.1') +
  facet_wrap(c(~year, ~TOD))+
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

2.2 Percentage of White Group between 2009 and 2017

ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09)) +
  geom_sf(aes(fill = q5(pctWhite))) +
  scale_fill_manual(values = palette5,
                    labels = qBr(allTracts.group, "pctWhite"),
                    name = "Percentage of White Group\n(Quintile Breaks)") +
  labs(title = "Percentage of White Group 2009-2017",
       subtitle = '',
       caption = 'Figure 2.2') +
  facet_wrap(c(~year,~TOD))+
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

2.3 Bachelor Degree Comparison between 2009 and 2017

ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = q5(pctBachelors))) +
  scale_fill_manual(values = palette5,
                    labels = qBr(allTracts.group, "pctBachelors"),
                    name = "Percentage of Bachelar Degree (%)\n(Quintile Breaks)") +
  labs(title = "Bachelor Degree Comparison between 2009-2017",
       subtitle = '',
       caption = 'Figure 2.3') +
  facet_wrap(c(~year,~TOD))+
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

2.4 Median Household Income Comparison between 2009 and 2017

ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = q5(MedHHInc))) +
  scale_fill_manual(values = palette5,
                    labels = qBr(allTracts.group, "MedHHInc"),
                    name = "Median Household Income\n(Quintile Breaks)") +
  labs(title = "Median Household Income 2009-2017",
       subtitle = 'Real dollars \n',
       caption = 'Figure 2.4') +
  facet_wrap(c(~year,~TOD))+
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

3. Bar Plot Comparisons

Grouped bar plots would provide a more precise and direct approach of examining the four variables over time. Some highlights are provided as follows:

  • Percentage of people with a Bachelor’s degree significantly increased for TOD tracts but remained the same level for those non-TOD tracts.
  • Median household income of TOD tracts and non-TOD tracts both increased but interestingly, income of non-TOD tracts was higher than TOD-tracts all the way. Similar trend can also be found in terms of median rent! Interesting!
  • Population of non-TOD tracts remained same level, but population of TOD tracts decreased and was smaller compared to non-TOD tracts all the way!
  • As indicated earlier, despite a slight increase of TOD- tracts in 2017, percentage of white group did not change a lot.
allTracts.Summary <- 
  st_drop_geometry(allTracts.group) %>%
  group_by(year, TOD) %>%
  summarize(Rent = mean(MedRent, na.rm = T),
            Population = mean(TotalPop, na.rm = T),
            `White Proprotion` = mean(pctWhite, na.rm = T),
            `Bachelor Degree` = mean(pctBachelors, na.rm = T),
            Income = mean(MedHHInc, na.rm = T))

allTracts.Summary %>%
  gather(Variable, Value, -year, -TOD) %>%
  ggplot(aes(year, Value, fill = TOD)) +
  geom_bar(stat = "identity", position = "dodge") +
  facet_wrap(~Variable, scales = "free", ncol = 3) +
  scale_fill_manual(values = c("#debae4", "#800694")) +
  labs(title = "Indicator differences across time and space \n",
       caption = 'Figure 3.1') +
  plotTheme() + 
  theme(legend.position="bottom") +
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

4. Table Comparisons

Despite being indirect in terms of visualization, the comparison table still serves as a good approach of extracting the exact values. Indicated in Table 4.1,

  • Although median rent of TOD tracts was lower compared to non-TOD tracts all the way, the increasing rate of TOD tracts is 5% higher (30% vs. 25% respectively), to some extent indicating a potential development.
  • Percentage of Bachelor Degree holder increased 36% in TOD tracts, while increased 17.2% in non-TOD tracts.
  • Median income increased 24% in TOD tracts, while increased 13.3% in non-TOD tracts.
allTracts.Summary %>%
  unite(year.TOD, year, TOD, sep = ": ", remove = T) %>%
  gather(Variable, Value, -year.TOD) %>%
  mutate(Value = round(Value, 2)) %>%
  spread(year.TOD, Value) %>%
  kable() %>%
  kable_styling() %>%
  footnote(general_title = "\n",
           general = "Table 4.1")
Variable 2009: Non-TOD 2009: TOD 2017: Non-TOD 2017: TOD
Bachelor Degree 0.58 1.13 0.68 1.54
Income 57573.74 47845.36 65220.27 59105.64
Population 5101.41 4479.06 5186.17 4255.57
Rent 756.44 706.39 947.34 921.06
White Proprotion 0.61 0.61 0.61 0.66

Table 4.1

5. Graduated Symbol Maps

We examined population and rent within 0.5 miles of each transit station. Generally speaking, distributions of populous areas and high rent areas might not be consistent. In terms of population, Victory Station (in the middle), Walnut Hill Station (Northwest), UNT Dallas Station (South) are sharing the three most population proportion, indicated in Figure 5.1. They are all relatively developed neighborhoods. When it comes to median rent, median rent in Downtown was comparatively higher compared to other areas, while rents of TOD tracts located along northeast Dallas were relatively higher as well. Accordingly, there is no direct relationship between population and rent in TOD tracts.

5.1 Population within 0.5 mile of each transit station

popByStation <-
  join_data %>% 
  group_by(STA_NAME) %>%
  summarize(`Total Population` = sum(TotalPop)) %>%
  dplyr::select(STA_NAME, `Total Population`) %>%
  st_drop_geometry() %>%
  left_join(D_Stops)

popByStation_sf <- popByStation %>%
  st_sf()

ggplot() + 
  geom_sf(data = Dallas, fill = 'lightgrey') +
  geom_sf(data = popByStation_sf, aes(size = `Total Population`), color = '#5e9bb2') +
  labs(title = "Graduated Symbol Map I: \nPopulation within 0.5 Mile of Each Transit Station" ,
       subtitle = '',
       caption = "Figure 5.1", 
       fill = 'Total Population') +
  plotTheme() + 
  theme(legend.position="right") + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

5.2 Median rent within 0.5 mile of each transit station

rentByStation <-
  join_data %>%
  group_by(STA_NAME) %>%
  summarize(`Median Rent` = median(na.omit(MedRent))) %>%
  dplyr::select(STA_NAME, `Median Rent`)  %>% 
  st_drop_geometry() %>%
  left_join(D_Stops)

rentByStation_sf <- rentByStation %>%
  st_sf()

ggplot() + 
  geom_sf(data = Dallas, fill = 'lightgrey') +
  geom_sf(data = rentByStation_sf, aes(size = `Median Rent`), color = '#c2a7ef')  +  
  labs(title = "Graduated Symbol Map II: \nMedian Rent within 0.5 Mile of Each Transit Station",
       subtitle = '',
       caption = "Figure 5.2") +
  plotTheme() + 
  theme(legend.position="right") + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold"))

6. Mean Rent as a function of distance to subway stations

The distance to transit station was another factor to examine the effects of TOD on rent level. Generally speaking, rent level of 2017 was higher than 2009 all the time, but the fluctuations of rent level with distance displayed a consistent pattern between 2009 and 2017. In each respective year, rent level was lowest when station was around 2 miles away from houses, while the highest rent level occurred when station was around 3 miles away from houses. Such a pattern might indicate that residents would not like to live so close to transit stations.

allTracts.rings <-
  st_join(st_centroid(dplyr::select(allTracts, GEOID, year)), 
          multipleRingBuffer(st_union(D_Stops), 47520, 2640)) %>%
  st_drop_geometry() %>%
  left_join(dplyr::select(allTracts, GEOID, MedRent, year), 
            by=c("GEOID"="GEOID", "year"="year")) %>%
  st_sf() %>%
  mutate(distance = distance / 5280) 

allTracts.rings <- na.omit(allTracts.rings)

mean_RENT <- allTracts.rings %>%
  group_by(year, distance) %>%
  summarize(mean_RENT = mean(MedRent)) 

ggplot(data = mean_RENT) +
  geom_line(aes(x = distance, y = mean_RENT, col = year), size = 2) + 
  geom_point(aes(x = distance, y = mean_RENT, col = year) , size = 4) + 
  labs(title = " Rent as a function of distance to subway stations",
       subtitle = '',
       caption = 'Figure 6.1') +
  scale_color_manual(values = c('#5f829e','#fab18e'),
                     name = 'Year') +
  theme(legend.position = "right") +
  xlab("Distance to Stations, Miles") + 
  ylab("Average Rents") +
  theme_classic() +
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

7. Crime Rate, Transit Access and Rents

Finally, we specifically examined the burglary crime rate in Dallas and its relationships towards rent level and TOD. Since this project to some extent focused on house choices, we leveraged the case-level burglary crime data, and found that burglary of habitation was the most prevalent burglary crime in Dallas. This finding provided us with useful insights. Indicated in Figure 7.1, burglary seemingly was more likely to clustered in the areas with lower rent level, such as southwest Dallas. And indicated in Figure 7.2, except for Northwest Dallas, early all crimes clustered inside TOD tracts.

# 1. Load Crime-Burglary Data
crime_dat <- read.csv('Dallas_Crime_Data.CSV')

crime_dat_point <- st_as_sf(crime_dat, coords = c("Longitude", "Latitude"), crs = 4326, agr = "constant") %>%
  select(Type, geometry)

# 2. Plot Relation between Burglary and Rents
ggplot(allTracts.group) + 
geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = q5(MedRent.inf))) +
  geom_sf(data = crime_dat_point, 
          aes(color = Type),
          show.legend = 'point',size = 1) +
  scale_fill_manual(values = palette5,
                    labels = qBr(allTracts.group, "MedRent.inf"),
                    name = "Rent\n(Quintile Breaks)")  +
  labs(title = "Relation between Burglary and Rents \n",
       caption = 'Figure 7.1') +
  theme(legend.position="right") +
  guides(fill=guide_legend(ncol=2)) +
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

# 3. Plot Relation between Burglary and TOD tracts
ggplot(allTracts.group)+
  geom_sf(data = st_union(tracts09))+
  geom_sf(aes(fill = TOD)) +
  scale_fill_manual(values = palette5) +
            
  geom_sf(data = crime_dat_point, 
          aes(color = Type),
          show.legend = 'point',size = 1) +
  labs(title = "Relation between Burglary and Transit \n",
       caption = 'Figure 7.2') +
  theme(legend.position="right") +
  mapTheme() + 
  theme(plot.title = element_text(color = "darkred", size=15, face="bold")) 

IV. Conclusion and Recommandation

To sum up, this analysis project has demonstrated the complex relationships that TOD can have on an evolving city, Dallas. Although non-TOD areas take the lead in median rents, household income and population density compared to TOD areas, the increasing rates of those indicators are comparatively higher, indicating a trend that Dallas residents are willing to live alongside TOD areas and get access to transit system and potential benefits. Even if burglary crime is comparatively prevalent across TOD areas, residents’ enthusiasm is still there. Among all the indicators, the percentage of residents with Bachelor’ degree shares the largest proportion in the TOD tracts, indicating that TOD areas indeed provide better environment for highly-educated group. Hence, to some extent, it is safe to conclude that residents in Dallas would like to pay more on rents for TOD.

Despite a plausible plan, the construction of TOD areas still needs to take up some challenges and address existing problems. For instance, it is necessary that City of Dallas should control the rapid increase of rent compared to the increase of household income, otherwise, despite being willing to live in TOD areas, residents cannot afford the crazy rent.

For the further study, city planners and policy makers should leverage zoning ordinances to help to incentivize TOD in areas that are considered strong candidates, and also take residents household sizes and types into consideration. Meanwhile, it’s also worth paying attention to the spatial biases could have on the result of this project since the areas of high rent could be clustering for reasons completely unrelated to transit development but to other policy-driven reasons.